var CompleteStatus = {
    incomplete: 'incomplete',
    complete: 'complete',
    future: 'future'
};

function Month(year, month, travelSteps, calendarNotes, monthDto) {
    this._year = year;
    this._month = month;
    this._missingTravelDays = [];
    this._completeStatus = CompleteStatus.incomplete;
    this._days = [];

    if (monthDto) {
        var me = this;
        this._completeStatus = monthDto.completeStatus;
        this._daysInMonth = monthDto.days.select(function(dayDto) {
            return new Day(dayDto, me);
        });
    }
    else {
        this._daysInMonth = this._createDaysInCurrentMonth();
    }
    this._createMonth();
    if (travelSteps) {
        this._travelSteps = travelSteps;
        this._connectTravelStepsToDays();
    }

    if (calendarNotes) {
        this._connectCalendarNotesToDays(calendarNotes);
    }
    this._createDayIndex();
}

Month.prototype = {
    getActivitySummary: function() {
        var activitySummary = new ActivitySummary();
        this._daysInMonth.foreach(function(dayModel) {
            var activities = dayModel.getActivities();
            activities.foreach(function(activity) {
                activitySummary[activity] += 1.0 / activities.length;
            });
        });
        return activitySummary;
    },

    getLocationSummary: function() {
        var locationSummary = new LocationSummary();
        this._daysInMonth.foreach(function(dayModel) {
            var locations = dayModel.getLocations()
                                    .where(function(location) { return location != null; })
                                    .select(function(location) { return location.fullname; });
            locationSummary.appendLocationsInOneDay(locations);
        });
        return locationSummary;
    },

    getTravelSteps: function() {
        return this._travelSteps ? this._travelSteps : [];
    },

    _createDayIndex: function() {
        this._dateIndex = {};
        for (var i = 0; i < this._days.length; i++) {
            var currentDay = this._days[i];
            this._dateIndex[currentDay.getDate().toString(CalendarConstants.JSONDateFormat)] = currentDay;
        }
    },
    
    _createMonth: function() {
        var last = this._createDaysInLastMonth();
        var current = this._daysInMonth;
        var next = this._createDaysInNextMonth();
        this._days =
        [].concat(last)
                .concat(current)
                .concat(next);
    },

    _createDayForRange: function(startDate, endDate) {
        var me = this;
        var days = [];
        var currentDay = startDate.clone();
        while (currentDay <= endDate) {
            days.push(new Day({ date: currentDay.clone() }, me));
            currentDay = currentDay.addDays(1);
        }
        return days;
    },
    _createDaysInLastMonth: function() {
        var firstDayOfMonthBlock = this.firstDayInMonth().addDays(-this._firstDayOffset());
        var firstDayInCurrentMonth = this.firstDayInMonth().addDays(-1);
        return this._createDayForRange(firstDayOfMonthBlock, firstDayInCurrentMonth);
    },
    _createDaysInCurrentMonth: function() {
        var firstDayInCurrentMonth = this.firstDayInMonth();
        var lastDayInCurrentMonth = this.lastDayInMonth();
        return this._createDayForRange(firstDayInCurrentMonth, lastDayInCurrentMonth);
    },
    _createDaysInNextMonth: function() {
        var lastDayInCurrentMonth = this.lastDayInMonth().addDays(1);
        var lastDayOfMonthBlock = this.lastDayInMonth().addDays(6 - this.lastDayInMonth().getDayOfWeek());
        return this._createDayForRange(lastDayInCurrentMonth, lastDayOfMonthBlock);
    },

    getCompleteStatus: function() {
        return this._completeStatus;
    },

    isCompleted: function() {
        return this._completeStatus == CompleteStatus.complete;
    },

    updateCompletenessInfo: function(previousMonth) {
        var me = this;
        var originalStatus = this._completeStatus;
        this._updateMissingTravelInfo();
        if (previousMonth !== undefined && previousMonth !== null) {
            this.updateMissingTravelInfoForFirstDay(previousMonth);
        }

        this._completeStatus = this._calculateCompleteStatus();
        return originalStatus !== this._completeStatus;
    },

    _todayIsNotTheLastDateOfMonth: function() {
         return this.getYearMonth().lastDayOfMonth() - new Date() != 0;
    },

    _calculateCompleteStatus: function() {
        if (this.getYearMonth().isLaterThan(new Date().toYearMonth())) {
            return CompleteStatus.future;
        }
        
        if (this.getYearMonth().isCurrentMonth() && this._todayIsNotTheLastDateOfMonth()) {
            return CompleteStatus.future;
        }

        var firstIncompleteDay = this._daysInMonth.findFirst(function(day) {
            return !day.isCompleted();
        });

        var missingTravel = this._hasMissingTravelDays();

        if (firstIncompleteDay || missingTravel) {
            return CompleteStatus.incomplete;
        }

        return CompleteStatus.complete;
    },

    getDaysCompleteStatus: function() {
        var me = this;
        return this._daysInMonth.select(function(day) {
            var dayStatus = day.getStatus();
            var missingTravel = me._missingTravelDays.contains(day.getDate());
            return dayStatus == CompleteStatus.complete && missingTravel ? CompleteStatus.incomplete : dayStatus;
        });
    },
    _updateMissingTravelInfo: function() {
        this._missingTravelDays = this._getMissingTravelInfoFor(this._daysInMonth);
    },

    _getMissingTravelInfoFor: function(days) {
        var missingTravelDays = [];
        for (var i = 0; i < days.length; i++) {
            var day = days[i];
            var previousDay = i === 0 ? null : days[i - 1];
            if (day.isMissingTravel(previousDay)) {
                missingTravelDays.push(day.getDate());
            }
        }
        return missingTravelDays;
    },

    updateMissingTravelInfoForFirstDay: function(prevMonth) {
        var previousDay = prevMonth.getLastDay();
        var currentDay = this.getFirstDay();
        if (currentDay.isMissingTravel(previousDay)) {
            this._missingTravelDays.push(currentDay.getDate());
            return true;
        }
        return false;
    },

    getFirstDay: function() {
        return this._daysInMonth[0];
    },

    getLastDay: function() {
        return this._daysInMonth[this._daysInMonth.length - 1];
    },

    getFullDays: function() {
        return this._days;
    },

    getDaysGroupedByWeek: function() {
        return this.getFullDays().split(function(day) {
            return day.getDate().isMonday()
        });
    },

    setInactiveDays: function(previousDays, nextDays) {
        this._days = [].concat(previousDays).concat(this._daysInMonth).concat(nextDays);
        this._createDayIndex();
    },

    getMissingTravelDays: function() {
        return this._missingTravelDays;
    },

    getFullMissingTravelDays: function() {
        //missing travel info for days in current month 
        //CONCAT
        //missing travel info for all days include inactive days, but not caculate the first inactive day and the day before that day in last month.
        return this._missingTravelDays.concat(this._getMissingTravelInfoFor(this._days)).unique();
    },

    _hasMissingTravelDays: function() {
        return this._missingTravelDays.length > 0;
    },

    isMissingTravelDay: function(date) {
        return this._missingTravelDays.contains(date);
    },

    toDto: function() {
        return {
            "year": this._year,
            "month": this._month,
            "completeStatus": this._completeStatus,
            "days": this._daysInMonth.select(function(day) {
                return day.toDto();
            })
        }
    },

    getYearMonth: function() {
        return new YearMonth(this._year, this._month);
    },

    
    getDay: function(date) {
        return this._dateIndex[date.toString(CalendarConstants.JSONDateFormat)];
    },

    eachDay: function(callback) {
        this._daysInMonth.foreach(function(day) {
            callback(day);
        })
    },

    eachDayDesc: function(callback) {
        var me = this;
        for (var i = this._daysInMonth.length - 1; i >= 0; i--) {
            callback(me._daysInMonth[i]);
        }
    },

    daysInRange: function(dateRange, order) {
        var results = [];
        var start = Date.Max(this.getFirstDay().getDate(), dateRange.start).clone().clearTime();
        var end = Date.Min(this.getLastDay().getDate(), dateRange.end).clone().clearTime();
        while (start <= end) {
            results.push(this.getDay(start));
            start.addDays(1);
        }

        if (order === 'desc') {
            results.reverse();
        }

        return results;
    },

    _connectTravelStepsToDays: function() {
        var me = this;
        var travelStepsInCurrentMonth =
                this._travelSteps.where(function(travelStep) {
                    return travelStep.containsDayInMonth(me._year, me._month);
                });
        travelStepsInCurrentMonth.foreach(function(travelStep) {
            me._daysInMonth.foreach(function(day) {
                if (travelStep.containsDay(day.getDate())) {
                    day.addTravelStep(travelStep);
                }
            });
        });
    },

    _connectCalendarNotesToDays: function(calendarNotes) {
        var me = this;
        var calendarNotesInCurrentMonth = calendarNotes.where(function(calendarNote) {
            return calendarNote.onMonth(me._year, me._month);
        });

        calendarNotesInCurrentMonth.foreach(function(calendarNote) {
            me._daysInMonth.foreach(function(day) {
                if (calendarNote.on(day.getDate())) {
                    day.setNote(calendarNote);
                }
            });
        });
    },

    firstDayInMonth: function() {
        return this.getYearMonth().firstDayOfMonth();
    },

    lastDayInMonth: function() {
        return this.getYearMonth().lastDayOfMonth();
    },

    _firstDayOffset: function() {
        return this.firstDayInMonth().getDayOfWeek();
    },

    getDayCountInMonth: function() {
        return this._daysInMonth.length;
    }
};
